<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the XML_Feed_Atom and XML_Feed_Atom_Entry classes.
 *
 * PHP version 5
 *
 * This file is part of PEAR::XML_Feed.
 *
 * PEAR::XML_Feed is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * PEAR::XML_Feed is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with PEAR::XML_Feed. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     XML_Feed
 * @category    XML
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007-2008 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Atom.php 6 2008-06-30 04:40:59Z mjijackson $
 */

require_once 'XML/Feed.php';

if (!defined('DATE_ATOM')) {
    define('DATE_ATOM', 'Y-m-d\\TH:i:sP'); // 2005-08-15T15:52:01+00:00
}

/**
 * Represents an Atom XML feed. This class and its related classes are based on
 * the Atom specification located at
 * {@link http://www.atomenabled.org/developers/syndication/ AtomEnabled}.
 *
 * @package     XML_Feed
 * @category    XML
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class XML_Feed_Atom extends XML_Feed
{

    /**
     * The default namespace that will be used.
     *
     * @var     string
     * @access  private
     */
    private $namespace = 'http://www.w3.org/2005/Atom';

    /**
     * Valid entity tags.
     *
     * @var     array
     * @access  private
     * @static
     */
    private static $valid_entity_tags = array(
        'author',
        'contributor'
    );

    /**
     * Valid text tags.
     *
     * @var     array
     * @access  private
     * @static
     */
    private static $valid_text_tags = array(
        'title',
        'subtitle',
        'summary',
        'content',
        'rights'
    );

    /**
     * Constructor. If $updated is a string, it will not be touched. If it's
     * numeric (timestamp in seconds) it will be converted to the appropriate
     * string format. If it is not provided, it will default to the current
     * time.
     *
     * @param   string  $id         This feed's id
     * @param   mixed   $title      This feed's title
     * @param   string  $updated    This feed's last update date
     * @access  public
     */
    public function __construct($id, $title, $updated = null)
    {
        parent::__construct();

        if (is_null($updated)) {
            $updated = time();
        }
        if (is_numeric($updated)) {
            $updated = date(DATE_ATOM, $updated);
        }
        if (!is_array($title)) {
            $title = array(
                'title' => $title,
                'type'  => 'text'
            );
        }

        $feed = $this->appendChild($this->createElement('feed'));
        $feed->appendChild($this->createElement('id', $id));
        $feed->appendChild($this->createText('title', $title['title'], $title['type']));
        $feed->appendChild($this->createElement('updated', $updated));
    }

    /**
     * Gets this feed's namespace.
     *
     * @return  string          This feed's namespace
     * @access  public
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * Creates an element in this feed's namespace.
     *
     * @param   string  $name       The tag name for the new node
     * @param   mixed   $value      The node value
     * @return  DOMElement          The newly created node
     * @access  public
     * @link    http://www.php.net/manual/function.dom-domdocument-createelement.php
     */
    public function createElement($name, $value = null)
    {
        return $this->createElementNS($this->getNamespace(), $name, $value);
    }

    /**
     * Creates an entity element. Entity elements may have <author> or
     * <contributor> tags.
     *
     * @param   string  $tag        The tag to use
     * @param   string  $name       The entity's name
     * @param   string  $email      The entity's email
     * @param   string  $uri        The entity's home page
     * @return  DOMElement          The newly created entity element
     * @throws  XML_Feed_Exception  If the entity tag is not valid
     * @access  public
     */
    public function createEntity($tag, $name, $email = null,
        $uri = null)
    {
        if (!in_array($tag, self::$valid_entity_tags)) {
            throw new XML_Feed_Exception("Invalid entity tag: $tag");
        }

        $entity = $this->createElement($tag);
        $entity->appendChild($this->createElement('name', $name));
        if ($email) {
            $entity->appendChild($this->createElement('email', $email));
        }
        if ($uri) {
            $entity->appendChild($this->createElement('uri', $uri));
        }

        return $entity;
    }

    /**
     * Creates a text element. Text elements may have <title>, <subtitle>,
     * <summary>, <content>, or <rights> tags.
     *
     * @param   string  $tag        The element tag to use
     * @param   string  $text       The element's value
     * @param   string  $type       The type of encoding to use for the text
     * @return  DOMElement          The newly created text element
     * @throws  XML_Feed_Exception  If the text tag is not valid
     * @access  public
     */
    public function createText($tag, $text, $type)
    {
        if (!in_array($tag, self::$valid_text_tags)) {
            throw new XML_Feed_Exception("Invalid text tag: $tag");
        }

        $text = $this->createElement($tag, $text);
        $text->setAttribute('type', $type);

        return $text;
    }

    /**
     * Creates a <link> element.
     *
     * @param   string      $href       The link's href
     * @param   string      $rel        The link relationship type
     * @param   string      $type       The media type of the linked resource
     * @param   string      $hreflang   The language of the linked resource
     * @param   string      $title      The link's title
     * @param   int         $length     The length of the resource in bytes
     * @return  DOMElement              The newly created <link> element
     * @access  public
     */
    public function createLink($href, $rel = null, $type = null,
        $hreflang = null, $title = null, $length = null)
    {
        $link = $this->createElement('link');
        $link->setAttribute('href', $href);
        if ($rel) {
            $link->setAttribute('rel', $rel);
        }
        if ($type) {
            $link->setAttribute('type', $type);
        }
        if ($hreflang) {
            $link->setAttribute('hreflang', $hreflang);
        }
        if ($title) {
            $link->setAttribute('title', $title);
        }
        if (is_numeric($length)) {
            $link->setAttribute('length', $length);
        }

        return $link;
    }

    /**
     * Creates a <category> element.
     *
     * @param   string      $term       The category's identifier
     * @param   string      $scheme     A URI that identifies the category's
     *                                  categorization scheme
     * @param   string      $label      A human-readable label for display
     * @return  DOMElement              The newly created <category> element
     * @access  public
     */
    public function createCategory($term, $scheme = null, $label = null)
    {
        $category = $this->createElement('category');
        $category->setAttribute('term', $term);
        if ($scheme) {
            $category->setAttribute('scheme', $scheme);
        }
        if ($label) {
            $category->setAttribute('label', $label);
        }

        return $category;
    }

    /**
     * Creates a <content> element.
     *
     * @param   string  $content        The content summary or URI where the
     *                                  content can be found
     * @param   string  $type           The encoding type of the content
     * @param   bool    $is_uri         True if the $content parameter specifies
     *                                  the content URI
     * @return  DOMElement              The newly created <content> element
     * @access  public
     */
    public function createContent($content, $type = 'text', $is_uri = false)
    {
        if ($is_uri == true) {
            $content = $this->createElement('content');
            $content->setAttribute('type', $type);
            $content->setAttribute('uri', $content);
            return $content;
        }

        return $this->createText('content', $content, $type);
    }

    /**
     * Appends an <author> element to this feed.
     *
     * @param   string  $name       The author's name
     * @param   string  $email      The author's email
     * @param   string  $uri        The author's home page
     * @return  DOMElement          The appended <author> element
     * @access  public
     */
    public function addAuthor($name, $email = null, $uri = null)
    {
        return $this->appendChild($this->createEntity('author', $name, $email, $uri));
    }

    /**
     * Appends a <contributor> element to this feed.
     *
     * @param   string  $name       The contributor's name
     * @param   string  $email      The contributor's email
     * @param   string  $uri        The contributor's home page
     * @return  DOMElement          The appended <contributor> element
     * @access  public
     */
    public function addContributor($name, $email = null, $uri = null)
    {
        return $this->appendChild($this->createEntity('contributor', $name, $email, $uri));
    }

    /**
     * Appends a <link> element to this feed.
     *
     * @param   string      $href       The link's href
     * @param   string      $rel        The link relationship type
     * @param   string      $type       The media type of the linked resource
     * @param   string      $hreflang   The language of the linked resource
     * @param   string      $title      The link's title
     * @param   int         $length     The length of the resource in bytes
     * @return  DOMElement              The appended <link> element
     * @access  public
     */
    public function addLink($href, $rel = null, $type = null, $hreflang = null,
        $title = null, $length = null)
    {
        return $this->appendChild($this->createLink($href, $rel, $type,
            $hreflang, $title, $length));
    }

    /**
     * Appends a <category> element to this feed.
     *
     * @param   string      $term       The category's identifier
     * @param   string      $scheme     A URI that identifies the category's
     *                                  categorization scheme
     * @param   string      $label      A human-readable label for display
     * @return  DOMElement              The appended <category> element
     * @access  public
     */
    public function addCategory($term, $scheme = null, $label = null)
    {
        return $this->appendChild($this->createCategory($term, $scheme, $label));
    }

    /**
     * Appends a <generator> element to this feed.
     *
     * @param   string      $name       The name of the generator
     * @param   string      $uri        The generator's uri
     * @param   string      $version    The generator's version number
     * @return  DOMElement              The appended <generator> element
     * @access  public
     */
    public function addGenerator($name, $uri = null, $version = null)
    {
        $generator = $this->createElement('generator', $name);
        if ($uri) {
            $generator->setAttribute('uri', $uri);
        }
        if (is_numeric($version)) {
            $generator->setAttribute('version', $version);
        }

        return $this->appendChild($generator);
    }

    /**
     * Appends an <icon> element to this feed.
     *
     * @param   string      $icon       The icon URI
     * @return  DOMElement              The appended <icon> element
     * @access  public
     */
    public function addIcon($icon)
    {
        return $this->appendChild($this->createElement('icon', $icon));
    }

    /**
     * Appends a <logo> element to this feed.
     *
     * @param   string      $logo       The logo URI
     * @return  DOMElement              The appended <logo> element
     * @access  public
     */
    public function addLogo($logo)
    {
        return $this->appendChild($this->createElement('logo', $logo));
    }

    /**
     * Appends a <rights> element to this feed.
     *
     * @param   string      $rights     Information about rights on the parent
     * @param   string      $type       The encoding type
     * @return  DOMElement              The appended <rights> element
     * @access  public
     */
    public function addRights($rights, $type = 'text')
    {
        return $this->appendChild($this->createText('rights', $rights, $type));
    }

    /**
     * Appends a <subtitle> element to this feed.
     *
     * @param   string      $subtitle   The subtitle's text
     * @param   string      $type       The encoding type
     * @return  DOMElement              The appended <subtitle> element
     * @access  public
     */
    public function addSubtitle($subtitle, $type = 'text')
    {
        return $this->appendChild($this->createText('subtitle', $subtitle, $type));
    }

    /**
     * Creates a new <entry> node and appends it to this feed. If the $updated
     * parameter is an integer, it will be treated as a timestamp and converted
     * to the appropriate format. If it is a string, it will not be touched. If
     * it is null, it will be set to the current time.
     *
     * @param   string          $id         The entry's id
     * @param   mixed           $title      The entry's title
     * @param   string          $updated    The entry's last update date
     * @return  XML_Feed_Atom_Entry         The appended <entry> element
     * @access  public
     */
    public function addEntry($id, $title, $updated = null)
    {
        return new XML_Feed_Atom_Entry($this, $id, $title, $updated);
    }

}

/**
 * Represents an <entry> element in an Atom feed.
 *
 * @package     XML_Feed
 * @category    XML
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class XML_Feed_Atom_Entry extends XML_Feed_Element
{

    /**
     * The feed this entry belongs to.
     *
     * @var     XML_Feed_Atom
     * @access  protected
     */
    protected $feed;

    /**
     * Constructor. If $updated is a string, it will not be touched. If it's
     * numeric (timestamp in seconds) it will be converted to the appropriate
     * string format. If it is not provided, it will default to the current
     * time. Upon creation, this entry will automatically be appended to the
     * given feed.
     *
     * @param   XML_Feed_Atom   $feed       This entry's parent feed
     * @param   string          $id         This entry's id
     * @param   mixed           $title      This entry's title
     * @param   string          $updated    This entry's last update date
     * @access  public
     */
    public function __construct(XML_Feed_Atom $feed, $id, $title, $updated = null)
    {
        parent::__construct('entry', null, $feed->getNamespace());

        if (is_null($updated)) {
            $updated = time();
        }
        if (is_numeric($updated)) {
            $updated = date(DATE_ATOM, $updated);
        }
        if (!is_array($title)) {
            $title = array(
                'title' => $title,
                'type'  => 'text'
            );
        }

        $this->feed = $feed;

        $feed->appendChild($this);
        $this->appendChild($feed->createElement('id', $id));
        $this->appendChild($feed->createText('title', $title['title'], $title['type']));
        $this->appendChild($feed->createElement('updated', $updated));
    }

    /**
     * Gets this entry's feed.
     *
     * @return  XML_Feed_Atom       This entry's feed
     * @access  public
     */
    public function feed()
    {
        return $this->feed;
    }

    /**
     * Appends an <author> element to this entry.
     *
     * @param   string  $name       The author's name
     * @param   string  $email      The author's email
     * @param   string  $uri        The author's home page
     * @return  DOMElement          The appended <author> element
     * @access  public
     */
    public function addAuthor($name, $email = null, $uri = null)
    {
        return $this->appendChild($this->feed->createEntity('author', $name, $email, $uri));
    }

    /**
     * Appends a <contributor> element to this entry.
     *
     * @param   string  $name       The contributor's name
     * @param   string  $email      The contributor's email
     * @param   string  $uri        The contributor's home page
     * @return  DOMElement          The appended <contributor> element
     * @access  public
     */
    public function addContributor($name, $email = null, $uri = null)
    {
        return $this->appendChild($this->feed->createEntity('contributor', $name, $email, $uri));
    }

    /**
     * Appends a <content> element to this entry.
     *
     * @param   string  $content        The content summary or URI where the
     *                                  content can be found
     * @param   string  $type           The encoding type of the content
     * @param   bool    $is_uri         True if the $content parameter specifies
     *                                  the content URI
     * @return  DOMElement              The appended <content> element
     * @access  public
     */
    public function addContent($content, $type = 'text', $is_uri = false)
    {
        return $this->appendChild($this->feed->createContent($content, $type, $is_uri));
    }

    /**
     * Appends a <link> element to this entry.
     *
     * @param   string      $href       The link's href
     * @param   string      $rel        The link relationship type
     * @param   string      $type       The media type of the linked resource
     * @param   string      $hreflang   The language of the linked resource
     * @param   string      $title      The link's title
     * @param   int         $length     The length of the resource in bytes
     * @return  DOMElement              The appended <link> element
     * @access  public
     */
    public function addLink($href, $rel = null, $type = null, $hreflang = null,
        $title = null, $length = null)
    {
        return $this->appendChild($this->feed->createLink($href, $rel, $type,
            $hreflang, $title, $length));
    }

    /**
     * Appends a <summary> element to this entry.
     *
     * @param   string  $summary        The summary text
     * @param   string  $type           The summary encoding type
     * @return  DOMElement              The appended <summary> element
     * @access  public
     */
    public function addSummary($summary, $type = 'text')
    {
        return $this->appendChild($this->feed->createText('summary', $summary, $type));
    }

    /**
     * Appends a <category> element to this entry.
     *
     * @param   string      $term       The category's identifier
     * @param   string      $scheme     A URI that identifies the category's
     *                                  categorization scheme
     * @param   string      $label      A human-readable label for display
     * @return  DOMElement              The appended <category> element
     * @access  public
     */
    public function addCategory($term, $scheme = null, $label = null)
    {
        return $this->appendChild($this->createCategory($term, $scheme, $label));
    }

    /**
     * Appends a <published> element to this entry. If $published is numeric
     * (timestamp in seconds) it will be converted to the proper format.
     *
     * @param   mixed       $published  The entry's initial creation timestamp
     * @return  DOMElement              The appended <published> element
     * @access  public
     */
    public function addPublished($published)
    {
        if (is_numeric($published)) {
            $published = date(DATE_ATOM, $published);
        }

        return $this->appendChild($this->createElement('published', $published));
    }

    /**
     * Appends a <source> element to this entry. The $source parameter must be
     * either a DOMNode or a well-formed XML string.
     *
     * @param   mixed       $source     The source feed
     * @return  DOMElement              The appended <source> element
     * @access  public
     */
    public function addSource($source)
    {
        if (is_string($source)) {
            $source = dom_import_simplexml(simplexml_load_string($source));
        }

        $src = $this->createElement('source');
        foreach ($source->getElementsByTagName('*') as $node) {
            if ($node->tagName != 'entry') {
                $src->appendChild($node->cloneNode(true));
            }
        }

        return $this->appendChild($src);
    }

    /**
     * Appends a <rights> element to this entry.
     *
     * @param   string      $rights     Information about rights on the parent
     * @param   string      $type       The encoding type
     * @return  DOMElement              The appended <rights> element
     * @access  public
     */
    public function addRights($rights, $type = 'text')
    {
        return $this->appendChild($this->feed->createText('rights', $rights, $type));
    }

}

?>
